package com.tau.egonetwork.threads;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import com.tau.egonetwork.algorithms.EgoNetworkGraph;
import com.tau.egonetwork.algorithms.GraphAdapter;
import com.tau.egonetwork.algorithms.GraphUtils;
import com.tau.egonetwork.algorithms.IShortestPathProgress;
import com.tau.egonetwork.common.Log;
import com.tau.egonetwork.excel.ExcelWriteAnalysisUtils;
import com.tau.egonetwork.exceptions.ExcelCastException;
import com.tau.egonetwork.gui.core.EgoNetworkMessageBox;
import com.tau.egonetwork.gui.core.EgoNetworkProgressDialog;
import com.tau.egonetwork.profiles.MainProfile;
import com.tau.egonetwork.threads.core.EgoNetworkCallable;
import com.tau.egonetwork.threads.core.ThreadPool;

public class AnalyzeDataThread<T extends Comparable<T>> {

	private EgoNetworkProgressDialog progressDialog;
	
	private Display display;
	private MainProfile<T> profile;
	
	private String filename;
	private boolean writeToExcel = true;
		
	private final static int PROGRESS_BUILD_GRAPH = 5;
	private final static int PROGRESS_COEFF = 10;
	private final static int PROGRESS_COEFF_NO_ROOT = 15;
	private final static int PROGRESS_DECOMPOSE = 25;
	private final static int PROGRESS_SHORTEST_PATHS = 90;

	private final static String PROGRESS_MSG_BUILD_GRAPH = "Building graph from profile network data";
	private final static String PROGRESS_MSG_COEFF = "Calculating clustering coefficients";
	private final static String PROGRESS_MSG_COEFF_NO_ROOT = "Calculating clustering coefficients without the egocenteric node";
	private final static String PROGRESS_MSG_DECOMPOSE = "Calculating k-shell decomposition";
	private final static String PROGRESS_MSG_SHORTEST_PATHS = "Calculating mean shortest path - reading profile ";
	private final static String PROGRESS_MSG_SHORTEST_PATHS_OUT_OF = " out of ";
	private final static String PROGRESS_MSG_BUILD_EXCEL = "Building excel output file";
		
	private AnalyzeDataThread(Shell parent, MainProfile<T> profile, String filename) {
		this.display = parent.getDisplay();
		this.profile = profile;
		this.filename = filename;

		progressDialog = new EgoNetworkProgressDialog(parent);
		progressDialog.setText("Data Analyze Process");
		progressDialog.setTitle("Analyzing Egocenteric Profile Data");
		progressDialog.setSubtitle("Analyzing statistical data. This may take several minutes...");
		progressDialog.setMessage(PROGRESS_MSG_BUILD_GRAPH);
	}
	
	public static <T extends Comparable<T>> void invoke(Shell parent, MainProfile<T> profile, String filename) {
		
		final AnalyzeDataThread<T> thread = new AnalyzeDataThread<T>(parent, profile, filename);
		thread.openDialog();
				
		ThreadPool.submitTask(new EgoNetworkCallable<Future<Void>, Void>() {
			
			@Override
			public Void call() throws Exception {
				thread.runThread();
				setArgument(null);
				return null;
			}
		});
		
	}
	
	private void openDialog() {
		progressDialog.open(false);
	}
		
	private void updateDisplay(final String message, final int progress) {
		display.asyncExec(new Runnable() {
			
			@Override
			public void run() {
				progressDialog.setMessage(message);
				progressDialog.setProgress(progress);
			}
		});
	}
	
	private void runThread() throws Exception {

		EgoNetworkGraph<T> graph = GraphAdapter.getGraph(profile);
		
		updateDisplay(PROGRESS_MSG_COEFF, PROGRESS_BUILD_GRAPH);		
		final Map<T, Double> coeffs = GraphUtils.clusteringCoefficients(graph);
		
		updateDisplay(PROGRESS_MSG_COEFF_NO_ROOT, PROGRESS_COEFF);
		final Map<T, Double> coeffsNoRoot = GraphUtils.clusteringCoefficientsNoRoot(
				graph, profile.getID());
		
		updateDisplay(PROGRESS_MSG_DECOMPOSE, PROGRESS_COEFF_NO_ROOT);
		final Map<T, Integer> shells = GraphUtils.kshellDecomposition(graph);
		final Map<T, Integer> shellsNoRoot = GraphUtils.kshellDecompositionNoRoot(
				graph, profile.getID());
		
		final int vertices = graph.getVertexCount();
		final int progress_bandwith = PROGRESS_SHORTEST_PATHS - PROGRESS_DECOMPOSE;
		updateDisplay(
				PROGRESS_MSG_SHORTEST_PATHS + 0 + PROGRESS_MSG_SHORTEST_PATHS_OUT_OF + vertices,
				PROGRESS_DECOMPOSE);
		final Map<T, Double> meanShortestPaths = GraphUtils.meanShortestPaths(
				graph,
				new IShortestPathProgress() {
					
					@Override
					public void calculting(int num) {
						updateDisplay(
								PROGRESS_MSG_SHORTEST_PATHS + num + PROGRESS_MSG_SHORTEST_PATHS_OUT_OF + vertices,
								PROGRESS_DECOMPOSE + (int)(progress_bandwith * (num / (double)vertices)));

					}
				});
				
		updateDisplay(PROGRESS_MSG_BUILD_EXCEL, PROGRESS_SHORTEST_PATHS);
		
		display.syncExec(new Runnable() {
			
			@Override
			public void run() {
				profile.storeAnalyzedData(coeffs, coeffsNoRoot, shells, shellsNoRoot, meanShortestPaths);
			}
		});
		
		while (writeToExcel) {
			
			try {
				
				ExcelWriteAnalysisUtils.saveStatistics(filename, profile, coeffs,
						coeffsNoRoot, shells, shellsNoRoot, meanShortestPaths);
				
				display.syncExec(new Runnable() {
					
					@Override
					public void run() {
						progressDialog.setProgress(100);
						progressDialog.close();
						
						//open the excel file you just created:
						Desktop desktop = Desktop.getDesktop();
						try {
							desktop.open(new File(filename));
						} catch (IOException e) {
							Log.writeExceptionToLog(e);
							EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(display.getActiveShell(), SWT.ERROR, SWT.OK);
							mbox.setText("Analyze Data");
							mbox.setMessage("Excel file created successfully. Unfortunately, cannot run it.");
							mbox.open();
						}
					}
				});
				
			} catch (final ExcelCastException e) {
				
				display.syncExec(new Runnable() {
					
					@Override
					public void run() {
						//check if exception is that file is open and give user a chance
						//to fix it:
						if (e.getCause() != null && e.getCause().getMessage() != null && e.getCause().getMessage().contains("another process")) {
							if (display.getActiveShell() != null) { //make sure shell wasn't already disposed
								EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(display.getActiveShell(), SWT.ICON_ERROR, SWT.RETRY | SWT.CANCEL);
								mbox.setText("Analyze Data - Writing to Excel error");
								mbox.setMessage("Analyzed data cannot be written since selected excel file is already open. Please close the file and retry (or select Cancel to cancel the entire analysis process)");
								if (mbox.open() == SWT.CANCEL) {
									//set progress to 100 so progress would actually close:
									progressDialog.setProgress(100);
									progressDialog.close();
									Log.writeLineToLog("User chose not to close open excel file.");
								}
							} else {
								throw e; //rethrow this exception
							}
						} else {
							//write to log and tell user:
							Log.writeExceptionToLog(e);
							boolean rethrow = false;
							if (display.getActiveShell() != null) {
								EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(display.getActiveShell(), SWT.ICON_ERROR, SWT.OK);
								mbox.setText("Error");
								mbox.setMessage(e.getMessage());
								mbox.open();
								rethrow = true;
							} 
							//set progress to 100 so progress would actually close:
							progressDialog.setProgress(100);
							progressDialog.close();
							
							if (rethrow)
								throw e;

						}
					}
				});
			} finally {
				display.syncExec(new Runnable() {
					
					@Override
					public void run() {
						writeToExcel = progressDialog.isActive();
					}
				});
			}
		}
	}
}